/*
 * ============================================================================
 *
 *       Filename:  netlayer.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2014年08月26日 15时55分51秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jianxi sun (jianxi), ycsunjane@gmail.com
 *   Organization:  
 *
 * ============================================================================
 */
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <signal.h>

#include <pthread.h>

#include "common.h"
#include "link.h"
#include "sockarr.h"
#include "netlayer.h"

static int tcp_alive(struct nettcp_t *tcp)
{

	int optval = 1;
	int optlen = sizeof(optval);
	if(setsockopt(tcp->sock, SOL_SOCKET, 
			SO_KEEPALIVE, &optval, optlen) < -1) {
		sys_err("Set tcp keepalive failed: %s\n",
			strerror(errno));
		return -1;
	}

	optval = 5;
	if(setsockopt(tcp->sock, SOL_TCP, 
			TCP_KEEPCNT, &optval, optlen) < -1) {
		sys_err("Set tcp_keepalive_probes failed: %s\n",
			strerror(errno));
		return -1;
	}

	optval = 5;
	if(setsockopt(tcp->sock, SOL_TCP, 
			TCP_KEEPIDLE, &optval, optlen) < -1) {
		sys_err("Set tcp_keepalive_time failed: %s\n",
			strerror(errno));
		return -1;
	}

	optval = 5;
	if(setsockopt(tcp->sock, SOL_TCP, 
			TCP_KEEPINTVL, &optval, optlen) < -1) {
		sys_err("Set tcp_keepalive_intvl failed: %s\n",
			strerror(errno));
		return -1;
	}

	return 0;
}

int tcp_connect(struct nettcp_t *tcp)
{
	int ret;
	if(tcp->addr.sin_addr.s_addr == 0) {
		sys_err("Specify address is error\n");
		return -1;
	}

	tcp->sock = socket(AF_INET, SOCK_STREAM, 0);
	if(tcp->sock < 0) {
		sys_err("Create tcp sock failed: %s\n",
			strerror(errno));
		return -1;
	}

	ret = tcp_alive(tcp);
	if(ret < 0) {
		sys_err("Set tcp alive failed\n");
		tcp_close(tcp);
		return -1;
	}

	socklen_t addr_len = sizeof(struct sockaddr_in);
	ret = connect(tcp->sock, (struct sockaddr *)&tcp->addr, addr_len);
	if(ret < 0) {
		tcp_close(tcp);
		sys_err("Connect ac failed: %s\n", strerror(errno));
		return -1;
	}

	return tcp->sock;
}

/* loop_rcv will read <size> to <buffer> */
int loop_rcv(int sock, void *buffer, int size)
{
	int len;
	while(size) {
		sys_debug("loop recving...\n");
		len = recv(sock, buffer, size, 0);
		if(len < 0 && errno == EINTR)
			continue;
		else if(len <= 0)
			return -1;
		size -= len;
	}
	return 0;
}

int tcp_rcv(struct nettcp_t *tcp, char **data, int *size)
{
	assert(data != NULL && tcp->sock >= 0);
	
	int ret;

	/* get message len */
	uint32_t messlen;
	ret = loop_rcv(tcp->sock, &messlen, sizeof(messlen));
	if(ret < 0) return ret;
	messlen = ntohl(messlen);
	sys_debug("tcp rcv message len: %d\n", messlen);

	char *mess = safe_malloc(messlen);
	ret = loop_rcv(tcp->sock, mess, messlen);
	if(ret < 0) {
		free(mess);
		return ret;
	}

	*data = mess;
	*size = messlen;
	return messlen;
}

int loop_send(int sock, char *buffer, int size)
{
	int len;
	while(size) {
		sys_debug("loop sending...\n");
		len = send(sock, buffer, size, 0);
		if(len < 0 && errno == EINTR)
			continue;
		else if(len < 0)
			return -1;
		size -= len;
	}

	return 0;
}

int tcp_sendpkt(struct nettcp_t *tcp, char *data, int size)
{
	assert(data != NULL && tcp->sock != -1);

	int ret;
	/* send message len */
	uint32_t sendlen = size;
	sendlen = htonl(sendlen);
	ret = loop_send(tcp->sock, (void *)&sendlen, sizeof(sendlen));
	if(ret < 0) return ret;

	/* send message */
	ret = loop_send(tcp->sock, data, size);
	if(ret < 0) return ret;

	sys_debug("Send packet success: %d\n", size);
	return size;
}

void tcp_close(struct nettcp_t *tcp)
{
	close(tcp->sock);
	tcp->sock = -1;
}

#define BACKLOG 	(512)
int tcp_listen(struct nettcp_t *tcp)
{
	int ret;

	tcp->sock = socket(AF_INET, SOCK_STREAM, 0);
	if(tcp->sock < 0) {
		sys_err("Create tcp sock failed: %s\n",
			strerror(errno));
		return -1;
	}

	int reuseaddr = 1;
	ret = setsockopt(tcp->sock, SOL_SOCKET, SO_REUSEADDR,
		&reuseaddr, sizeof(reuseaddr));
	if(ret < 0) {
		sys_err("set sock reuse failed: %s\n",
			strerror(errno));
		return -1;
	}

	socklen_t socklen = sizeof(struct sockaddr_in);
	ret = bind(tcp->sock, 
		(struct sockaddr *)&tcp->addr, socklen);
	if(ret < 0) {
		sys_err("Bind tcp sock failed: %s\n",
			strerror(errno));
		tcp_close(tcp);
		return -1;
	}

	ret = listen(tcp->sock, BACKLOG);
	if(ret < 0) {
		sys_err("Bind tcp sock failed: %s\n",
			strerror(errno));
		tcp_close(tcp);
		return -1;
	}

	return tcp->sock;
}

int tcp_accept(struct nettcp_t *tcp, int func(void *))
{
	int clisock;
	clisock = accept(tcp->sock, NULL, NULL);
	if(clisock < 0) {
		sys_err("Accept tcp sock failed: %s\n",
			strerror(errno));
		return -1;
	}

	sys_debug("TCP listen new client connect: %d\n", clisock);
	sockarr_insert(clisock, func, NULL);
	return clisock;
}
